

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=dark>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/fluid.png">
  <link rel="icon" href="/img/icon/dw.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="Memory">
  <meta name="keywords" content="">
  
    <meta name="description" content="🥝 技巧分享和最佳实践，为你呈现完美的前后端交互体验，让你的项目焕发新活力">
<meta property="og:type" content="article">
<meta property="og:title" content="实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信">
<meta property="og:url" content="http://example.com/2023/09/13/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%EF%BC%9A%E4%BD%BF%E7%94%A8%20Spring%20Boot%20%E9%9B%86%E6%88%90%20WebSocket%20%E5%AE%9E%E7%8E%B0%E5%89%8D%E5%90%8E%E7%AB%AF%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/index.html">
<meta property="og:site_name" content="Memory&#39;s blog">
<meta property="og:description" content="🥝 技巧分享和最佳实践，为你呈现完美的前后端交互体验，让你的项目焕发新活力">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://example.com/img/artical/girl11.jpg">
<meta property="article:published_time" content="2023-09-13T11:07:48.000Z">
<meta property="article:modified_time" content="2023-09-30T04:42:14.000Z">
<meta property="article:author" content="Memory">
<meta property="article:tag" content="经验">
<meta property="article:tag" content="SpringBoot">
<meta property="article:tag" content="WebSocket">
<meta property="article:tag" content="实时通信">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="http://example.com/img/artical/girl11.jpg">
  
  
  
  <title>实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信 - Memory&#39;s blog</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"example.com","root":"/","version":"1.9.4","typing":{"enable":true,"typeSpeed":100,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":false,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
  

  

  

  

  

  

  

  



  
<meta name="generator" content="Hexo 5.4.2"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 75vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/">
      <strong>Memory&#39;s blog</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                <span>首页</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                <span>归档</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                <span>分类</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                <span>标签</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                <span>关于</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/links/">
                <i class="iconfont icon-link-fill"></i>
                <span>友链</span>
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              <i class="iconfont icon-search"></i>
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">
              <i class="iconfont icon-dark" id="color-toggle-icon"></i>
            </a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/img/newBG/girl.jpg') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信"></span>
          
        </div>

        
          
  <div class="mt-3">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-author" aria-hidden="true"></i>
        Memory
      </span>
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2023-09-13 19:07" pubdate>
          2023年9月13日 晚上
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          20k 字
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          51 分钟
        
      </span>
    

    
    
      
        <span id="leancloud-page-views-container" class="post-meta" style="display: none">
          <i class="iconfont icon-eye" aria-hidden="true"></i>
          <span id="leancloud-page-views"></span> 次
        </span>
        
      
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar category-bar" style="margin-right: -1rem">
    





<div class="category-list">
  
  
    
    
    
    <div class="category row nomargin-x">
      <a class="category-item 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="经验"
        id="heading-32a8e000e928e6dc255876d9fc0178b3" role="tab" data-toggle="collapse" href="#collapse-32a8e000e928e6dc255876d9fc0178b3"
        aria-expanded="true"
      >
        经验
        <span class="list-group-count">(7)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-32a8e000e928e6dc255876d9fc0178b3"
           role="tabpanel" aria-labelledby="heading-32a8e000e928e6dc255876d9fc0178b3">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Blog"
        id="heading-be8df1f28c0abc85a0ed0c6860e5d832" role="tab" data-toggle="collapse" href="#collapse-be8df1f28c0abc85a0ed0c6860e5d832"
        aria-expanded="false"
      >
        Blog
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-be8df1f28c0abc85a0ed0c6860e5d832"
           role="tabpanel" aria-labelledby="heading-be8df1f28c0abc85a0ed0c6860e5d832">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Docs"
        id="heading-a3907cd461d8739aa3266047bc4b8c0c" role="tab" data-toggle="collapse" href="#collapse-a3907cd461d8739aa3266047bc4b8c0c"
        aria-expanded="false"
      >
        Docs
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-a3907cd461d8739aa3266047bc4b8c0c"
           role="tabpanel" aria-labelledby="heading-a3907cd461d8739aa3266047bc4b8c0c">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/2023/05/27/%E5%A4%A7%E9%81%93%E8%87%B3%E7%AE%80%EF%BC%9A%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2%E4%B8%8E%E6%96%87%E6%A1%A3%E7%AB%99%E7%82%B9%E7%9A%84%E7%BB%88%E6%9E%81%E6%8C%87%E5%8D%971.0/" title="大道至简：快速搭建博客与文档站点的终极指南1.0"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">大道至简：快速搭建博客与文档站点的终极指南1.0</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
        
      </div>
    </div>
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Linux"
        id="heading-edc9f0a5a5d57797bf68e37364743831" role="tab" data-toggle="collapse" href="#collapse-edc9f0a5a5d57797bf68e37364743831"
        aria-expanded="false"
      >
        Linux
        <span class="list-group-count">(2)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-edc9f0a5a5d57797bf68e37364743831"
           role="tabpanel" aria-labelledby="heading-edc9f0a5a5d57797bf68e37364743831">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/2023/06/20/%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/" title="宝塔面板安装教程"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">宝塔面板安装教程</span>
        </a>
      
    
      
      
        <a href="/2023/06/20/%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%EF%BC%9A%E8%A7%A3%E6%94%BE%E6%89%8B%E5%8A%A8%E9%85%8D%E7%BD%AE%EF%BC%8C%E8%BD%BB%E6%9D%BE%E7%AE%A1%E7%90%86%E4%BD%A0%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8/" title="宝塔面板：解放手动配置，轻松管理你的服务器"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">宝塔面板：解放手动配置，轻松管理你的服务器</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="SpringBoot"
        id="heading-c8428a45869e78fb361ed20b9c23b0a8" role="tab" data-toggle="collapse" href="#collapse-c8428a45869e78fb361ed20b9c23b0a8"
        aria-expanded="true"
      >
        SpringBoot
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-c8428a45869e78fb361ed20b9c23b0a8"
           role="tabpanel" aria-labelledby="heading-c8428a45869e78fb361ed20b9c23b0a8">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="WebSocket"
        id="heading-45edc1b96407d9d213dd1135962a32a0" role="tab" data-toggle="collapse" href="#collapse-45edc1b96407d9d213dd1135962a32a0"
        aria-expanded="true"
      >
        WebSocket
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-45edc1b96407d9d213dd1135962a32a0"
           role="tabpanel" aria-labelledby="heading-45edc1b96407d9d213dd1135962a32a0">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="实时通信"
        id="heading-60007a28c719f04273eb55e0d8b0d250" role="tab" data-toggle="collapse" href="#collapse-60007a28c719f04273eb55e0d8b0d250"
        aria-expanded="true"
      >
        实时通信
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-60007a28c719f04273eb55e0d8b0d250"
           role="tabpanel" aria-labelledby="heading-60007a28c719f04273eb55e0d8b0d250">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/2023/09/13/%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%EF%BC%9A%E4%BD%BF%E7%94%A8%20Spring%20Boot%20%E9%9B%86%E6%88%90%20WebSocket%20%E5%AE%9E%E7%8E%B0%E5%89%8D%E5%90%8E%E7%AB%AF%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/" title="实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信"
           class="list-group-item list-group-item-action
           active">
          <span class="category-post">实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
        
      </div>
    </div>
  
        
      </div>
    </div>
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="文档站点生成"
        id="heading-bec1410d37e10a04abf5418a2fbe7242" role="tab" data-toggle="collapse" href="#collapse-bec1410d37e10a04abf5418a2fbe7242"
        aria-expanded="false"
      >
        文档站点生成
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-bec1410d37e10a04abf5418a2fbe7242"
           role="tabpanel" aria-labelledby="heading-bec1410d37e10a04abf5418a2fbe7242">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Blog"
        id="heading-be8df1f28c0abc85a0ed0c6860e5d832" role="tab" data-toggle="collapse" href="#collapse-be8df1f28c0abc85a0ed0c6860e5d832"
        aria-expanded="false"
      >
        Blog
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-be8df1f28c0abc85a0ed0c6860e5d832"
           role="tabpanel" aria-labelledby="heading-be8df1f28c0abc85a0ed0c6860e5d832">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/2023/06/24/%E5%A4%A7%E9%81%93%E8%87%B3%E7%AE%80%EF%BC%9A%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2%E4%B8%8E%E6%96%87%E6%A1%A3%E7%AB%99%E7%82%B9%E7%9A%84%E7%BB%88%E6%9E%81%E6%8C%87%E5%8D%972.0/" title="大道至简：快速搭建博客与文档站点的终极指南2.0"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">大道至简：快速搭建博客与文档站点的终极指南2.0</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
        
      </div>
    </div>
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="站点配置"
        id="heading-fe7455365087e71d4eae986298cfe438" role="tab" data-toggle="collapse" href="#collapse-fe7455365087e71d4eae986298cfe438"
        aria-expanded="false"
      >
        站点配置
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-fe7455365087e71d4eae986298cfe438"
           role="tabpanel" aria-labelledby="heading-fe7455365087e71d4eae986298cfe438">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Blog"
        id="heading-be8df1f28c0abc85a0ed0c6860e5d832" role="tab" data-toggle="collapse" href="#collapse-be8df1f28c0abc85a0ed0c6860e5d832"
        aria-expanded="false"
      >
        Blog
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-be8df1f28c0abc85a0ed0c6860e5d832"
           role="tabpanel" aria-labelledby="heading-be8df1f28c0abc85a0ed0c6860e5d832">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/2023/06/26/%E5%A4%A7%E9%81%93%E8%87%B3%E7%AE%80%EF%BC%9A%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2%E4%B8%8E%E6%96%87%E6%A1%A3%E7%AB%99%E7%82%B9%E7%9A%84%E7%BB%88%E6%9E%81%E6%8C%87%E5%8D%973-0/" title="大道至简：快速搭建博客与文档站点的终极指南3.0"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">大道至简：快速搭建博客与文档站点的终极指南3.0</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
        
      </div>
    </div>
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="表达"
        id="heading-4c454216db3f190289572e8d57246bef" role="tab" data-toggle="collapse" href="#collapse-4c454216db3f190289572e8d57246bef"
        aria-expanded="false"
      >
        表达
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-4c454216db3f190289572e8d57246bef"
           role="tabpanel" aria-labelledby="heading-4c454216db3f190289572e8d57246bef">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/2023/11/18/%E5%80%BE%E8%AF%89%E5%BF%83%E5%A3%B0%EF%BC%8C%E6%B2%9F%E9%80%9A%E4%B9%8B%E9%81%93%EF%BC%9A%E4%BA%BA%E9%99%85%E4%BA%A4%E5%BE%80%E4%B8%AD%E7%9A%84%E8%A1%A8%E8%BE%BE%E6%8A%80%E5%B7%A7/" title="倾诉心声，沟通之道：人际交往中的表达技巧"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">倾诉心声，沟通之道：人际交往中的表达技巧</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
        
      </div>
    </div>
  
</div>


  </aside>


    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信</h1>
            
              <p class="note note-success">
                
                  
                    本文最后更新于：8 个月前
                  
                
              </p>
            
            
              <div class="markdown-body">
                
                <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><ul>
<li><h5 id="经过三周的沉寂，我终于迎来了个人博客上全新一篇的契机"><a href="#经过三周的沉寂，我终于迎来了个人博客上全新一篇的契机" class="headerlink" title="经过三周的沉寂，我终于迎来了个人博客上全新一篇的契机"></a>经过三周的沉寂，我终于迎来了个人博客上全新一篇的契机</h5></li>
<li><h5 id="今天，我终于有机会探讨-Spring-Boot-集成-WebSocket，并将其记录在博客中"><a href="#今天，我终于有机会探讨-Spring-Boot-集成-WebSocket，并将其记录在博客中" class="headerlink" title="今天，我终于有机会探讨 Spring Boot 集成 WebSocket，并将其记录在博客中"></a>今天，我终于有机会探讨 Spring Boot 集成 WebSocket，并将其记录在博客中</h5></li>
<li><h5 id="无比兴奋的我，废话不多说，期待与读者们分享这一激动人心的主题"><a href="#无比兴奋的我，废话不多说，期待与读者们分享这一激动人心的主题" class="headerlink" title="无比兴奋的我，废话不多说，期待与读者们分享这一激动人心的主题"></a>无比兴奋的我，废话不多说，期待与读者们分享这一激动人心的主题</h5></li>
</ul>
<h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><ul>
<li><h5 id="提供以下链接以供参考："><a href="#提供以下链接以供参考：" class="headerlink" title="提供以下链接以供参考："></a>提供以下链接以供参考：</h5></li>
<li><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/u011974797/article/details/130055062">SpringBoot集成WebSocket进行前后端通信_谢小涛的博客-CSDN博客</a></p>
</li>
</ul>
<h2 id="WebSocket"><a href="#WebSocket" class="headerlink" title="WebSocket"></a>WebSocket</h2><ul>
<li><h5 id="什么是WebSocket？"><a href="#什么是WebSocket？" class="headerlink" title="什么是WebSocket？"></a>什么是WebSocket？</h5></li>
<li><h5 id="38-WebSocket：沙盒里的TCP-lianglianglee-com-https-learn-lianglianglee-com-专栏-透视HTTP协议-38-WebSocket：沙盒里的TCP-md"><a href="#38-WebSocket：沙盒里的TCP-lianglianglee-com-https-learn-lianglianglee-com-专栏-透视HTTP协议-38-WebSocket：沙盒里的TCP-md" class="headerlink" title="[38 WebSocket：沙盒里的TCP (lianglianglee.com)](https://learn.lianglianglee.com/专栏/透视HTTP协议/38  WebSocket：沙盒里的TCP.md)"></a>[38 WebSocket：沙盒里的TCP (lianglianglee.com)](<a target="_blank" rel="noopener" href="https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E9%80%8F%E8%A7%86HTTP%E5%8D%8F%E8%AE%AE/38">https://learn.lianglianglee.com/专栏/透视HTTP协议/38</a>  WebSocket：沙盒里的TCP.md)</h5></li>
<li><p>简单来讲，<strong>WebSocket 是应用层协议</strong>，用来解决在 HTTP 协议中，在<strong>请求 - 应答 通信模式</strong>下，服务器只能<strong>被动接受客户端的请求</strong>，而<strong>无法主动向客户端发送请求</strong>的问题，它保障了<strong>实时通信</strong>的模式 </p>
</li>
<li><p>我们需要了解到的有关WebSocket的知识：<strong>协议名、报文结构、连接握手</strong>等 <strong>（2023/09/30早）</strong></p>
</li>
</ul>
<h2 id="项目场景"><a href="#项目场景" class="headerlink" title="项目场景"></a>项目场景</h2><ul>
<li><h5 id="最近在开发新的网站：MemoryChat，计划由伙伴匹配系统从移动端实现改为PC端，并提供博文分享、组队交流等功能"><a href="#最近在开发新的网站：MemoryChat，计划由伙伴匹配系统从移动端实现改为PC端，并提供博文分享、组队交流等功能" class="headerlink" title="最近在开发新的网站：MemoryChat，计划由伙伴匹配系统从移动端实现改为PC端，并提供博文分享、组队交流等功能"></a>最近在开发新的网站：MemoryChat，计划由伙伴匹配系统从移动端实现改为PC端，并提供博文分享、组队交流等功能</h5></li>
<li><p>其中最为核心的，就是<strong>实现用户间的实时通信</strong></p>
</li>
</ul>
<h2 id="实现步骤（Version1-0）"><a href="#实现步骤（Version1-0）" class="headerlink" title="实现步骤（Version1.0）"></a>实现步骤（Version1.0）</h2><ul>
<li><h5 id="这里仅展示核心业务代码，详细的业务流程会在后续讲解"><a href="#这里仅展示核心业务代码，详细的业务流程会在后续讲解" class="headerlink" title="这里仅展示核心业务代码，详细的业务流程会在后续讲解"></a>这里仅展示核心业务代码，详细的业务流程会在后续讲解</h5></li>
</ul>
<h3 id="后端"><a href="#后端" class="headerlink" title="后端"></a>后端</h3><ul>
<li><h5 id="依赖导入（2023-09-13晚）"><a href="#依赖导入（2023-09-13晚）" class="headerlink" title="依赖导入（2023/09/13晚）"></a>依赖导入（2023/09/13晚）</h5></li>
</ul>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>          <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>          <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-websocket<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>      <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="配置WebSocket"><a href="#配置WebSocket" class="headerlink" title="配置WebSocket"></a>配置WebSocket</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WebSocketConfig</span> &#123;<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> ServerEndpointExporter <span class="hljs-title function_">serverEndpointExporter</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ServerEndpointExporter</span>();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="核心业务代码"><a href="#核心业务代码" class="headerlink" title="核心业务代码"></a>核心业务代码</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.memory.usercenter.common;<br><br><span class="hljs-keyword">import</span> com.google.gson.Gson;<br><span class="hljs-keyword">import</span> com.memory.usercenter.model.DTO.Message;<br><span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;<br><span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;<br><span class="hljs-keyword">import</span> org.springframework.data.redis.core.RedisTemplate;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Service;<br><br><span class="hljs-keyword">import</span> javax.annotation.Resource;<br><span class="hljs-keyword">import</span> javax.websocket.*;<br><span class="hljs-keyword">import</span> javax.websocket.server.PathParam;<br><span class="hljs-keyword">import</span> javax.websocket.server.ServerEndpoint;<br><span class="hljs-keyword">import</span> java.io.IOException;<br><span class="hljs-keyword">import</span> java.net.http.WebSocket;<br><span class="hljs-keyword">import</span> java.util.concurrent.CopyOnWriteArraySet;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> 邓哈哈</span><br><span class="hljs-comment"> * 2023/9/13 13:19</span><br><span class="hljs-comment"> * Function:</span><br><span class="hljs-comment"> * @ ServerEndpoint 注解是一个类层次的注解，它的功能主要是将目前的类定义成一个websocket服务器端,</span><br><span class="hljs-comment"> * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端</span><br><span class="hljs-comment"> * Version 1.0</span><br><span class="hljs-comment"> */</span><br><br><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@ServerEndpoint(&quot;/websocket/&#123;sid&#125;&quot;)</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WebSocketServer</span> &#123;<br>    <span class="hljs-comment">// Redis 负责存储离线消息</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> RedisTemplate&lt;String, Object&gt; redisTemplate = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RedisTemplate</span>&lt;&gt;();<br><br>    <span class="hljs-meta">@Resource</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setRedisTemplate</span><span class="hljs-params">(RedisTemplate redisTemplate)</span> &#123;<br>        WebSocketServer.redisTemplate = redisTemplate;<br>    &#125;<br><br>    <span class="hljs-comment">//静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">onlineCount</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>    <span class="hljs-comment">//concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> CopyOnWriteArraySet&lt;WebSocketServer&gt; webSocketSet = <span class="hljs-keyword">new</span> <span class="hljs-title class_">CopyOnWriteArraySet</span>&lt;WebSocketServer&gt;();<br>    <span class="hljs-comment">//与某个客户端的连接会话，需要通过它来给客户端发送数据</span><br>    <span class="hljs-keyword">private</span> Session session;<br>    <span class="hljs-comment">//接收sid</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">String</span> <span class="hljs-variable">sid</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;&quot;</span>;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 连接建立成功调用的方法</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@OnOpen</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onOpen</span><span class="hljs-params">(Session session, <span class="hljs-meta">@PathParam(&quot;sid&quot;)</span> String sid)</span> &#123;<br>        <span class="hljs-built_in">this</span>.session = session;<br>        webSocketSet.add(<span class="hljs-built_in">this</span>); <span class="hljs-comment">//加入set中</span><br>        <span class="hljs-built_in">this</span>.sid = sid;<br>        addOnlineCount(); <span class="hljs-comment">//在线数加1</span><br>        <span class="hljs-keyword">try</span> &#123;<br>            sendMessage(<span class="hljs-string">&quot;conn_success&quot;</span>);<br>            log.info(<span class="hljs-string">&quot;有新窗口开始监听:&quot;</span> + sid + <span class="hljs-string">&quot;,当前在线人数为:&quot;</span> + getOnlineCount());<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            log.error(<span class="hljs-string">&quot;websocket IO Exception&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 连接关闭调用的方法</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@OnClose</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onClose</span><span class="hljs-params">()</span> &#123;<br>        webSocketSet.remove(<span class="hljs-built_in">this</span>); <span class="hljs-comment">//从set中删除</span><br>        subOnlineCount(); <span class="hljs-comment">//在线数减1</span><br>        <span class="hljs-comment">//断开连接情况下，更新主板占用情况为释放</span><br>        log.info(<span class="hljs-string">&quot;释放的sid为：&quot;</span> + sid);<br>        <span class="hljs-comment">//这里写你 释放的时候，要处理的业务</span><br>        log.info(<span class="hljs-string">&quot;有一连接关闭！当前在线人数为&quot;</span> + getOnlineCount());<br><br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 收到客户端消息后调用的方法</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * @ Param message 客户端发送过来的消息</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@OnMessage</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(String message, Session session)</span> &#123;<br>        log.info(<span class="hljs-string">&quot;收到来自窗口&quot;</span> + sid + <span class="hljs-string">&quot;的信息:&quot;</span> + message);<br>        <span class="hljs-comment">// 将 JSON 字符串 转成 Java 对象</span><br>        <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>        <span class="hljs-type">Message</span> <span class="hljs-variable">newMessage</span> <span class="hljs-operator">=</span> gson.fromJson(message, Message.class);<br><br>        <span class="hljs-type">Long</span> <span class="hljs-variable">senderId</span> <span class="hljs-operator">=</span> newMessage.getSenderId();<br>        <span class="hljs-type">Long</span> <span class="hljs-variable">receiverId</span> <span class="hljs-operator">=</span> newMessage.getReceiverId();<br>        <span class="hljs-comment">// 群发消息</span><br>        <span class="hljs-comment">// 存储信息到Redis 30min</span><br>        redisTemplate.opsForValue().set(<span class="hljs-string">&quot;1&quot;</span>, <span class="hljs-string">&quot;2&quot;</span>);<br><span class="hljs-comment">//        ops.set(&quot;memory:user:message:&quot; + senderId, message, 30, TimeUnit.MINUTES);</span><br><span class="hljs-comment">//        ops.set(&quot;memory:user:message:&quot; + receiverId, message, 30, TimeUnit.MINUTES);</span><br>        <span class="hljs-keyword">for</span> (WebSocketServer item : webSocketSet) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                item.sendMessage(message);<br>            &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                e.printStackTrace();<br>            &#125;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * @ Param session</span><br><span class="hljs-comment">     * @ Param error</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@OnError</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onError</span><span class="hljs-params">(Session session, Throwable error)</span> &#123;<br>        log.error(<span class="hljs-string">&quot;发生错误&quot;</span>);<br>        error.printStackTrace();<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 实现服务器主动推送</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sendMessage</span><span class="hljs-params">(String message)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>        <span class="hljs-built_in">this</span>.session.getBasicRemote().sendText(message);<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 群发自定义消息</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sendInfo</span><span class="hljs-params">(String message, <span class="hljs-meta">@PathParam(&quot;sid&quot;)</span> String sid)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>        log.info(<span class="hljs-string">&quot;推送消息到窗口&quot;</span> + sid + <span class="hljs-string">&quot;，推送内容:&quot;</span> + message);<br><br>        <span class="hljs-keyword">for</span> (WebSocketServer item : webSocketSet) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-comment">//这里可以设定只推送给这个sid的，为null则全部推送</span><br>                <span class="hljs-keyword">if</span> (sid == <span class="hljs-literal">null</span>) &#123;<br><span class="hljs-comment">// item.sendMessage(message);</span><br>                &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (item.sid.equals(sid)) &#123;<br>                    item.sendMessage(message);<br>                &#125;<br>            &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                <span class="hljs-keyword">continue</span>;<br>            &#125;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getOnlineCount</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> onlineCount;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addOnlineCount</span><span class="hljs-params">()</span> &#123;<br>        WebSocketServer.onlineCount++;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">subOnlineCount</span><span class="hljs-params">()</span> &#123;<br>        WebSocketServer.onlineCount--;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> CopyOnWriteArraySet&lt;WebSocketServer&gt; <span class="hljs-title function_">getWebSocketSet</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> webSocketSet;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h3 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h3><ul>
<li><h5 id="我前端是使用Vue语法，并且引入了Ant-Design-Vue组件（2023-09-13晚）"><a href="#我前端是使用Vue语法，并且引入了Ant-Design-Vue组件（2023-09-13晚）" class="headerlink" title="我前端是使用Vue语法，并且引入了Ant Design Vue组件（2023/09/13晚）"></a>我前端是使用Vue语法，并且引入了Ant Design Vue组件（2023/09/13晚）</h5></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;template&gt;<br>  &lt;div&gt;<br>    Welcome<br>    &lt;br/&gt;<br>    &lt;input id=&quot;text&quot; value=&quot;这是一条测试消息&quot;/&gt;<br>    &lt;a-button @click=&quot;sendMessage&quot;&gt;发送消息&lt;/a-button&gt;<br>    &lt;hr/&gt;<br>    &lt;a-button @click=&quot;openSocket()&quot;&gt;连接socket&lt;/a-button&gt;<br>    &lt;a-button @click=&quot;closeWebSocket()&quot;&gt;关闭WebSocket连接&lt;/a-button&gt;<br>    &lt;hr/&gt;<br>    &lt;div id=&quot;message&quot;&gt;&#123;&#123; retMes &#125;&#125;&lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br></pre></td><td class="code"><pre><code class="hljs ts">&lt;script setup&gt;<br><span class="hljs-keyword">import</span> &#123;ref&#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;vue&quot;</span>;<br><span class="hljs-keyword">import</span> currentUser <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;@/model/currentUser&quot;</span>;<br><br><span class="hljs-keyword">const</span> retMes = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&quot;&quot;</span>);<br><span class="hljs-keyword">const</span> currentUserId = currentUser.<span class="hljs-property">value</span>.<span class="hljs-property">id</span>;<br><span class="hljs-keyword">let</span> socket;<br><br><span class="hljs-keyword">const</span> initMessage = &#123;<br>  <span class="hljs-string">&quot;senderId&quot;</span>: <span class="hljs-string">&quot;&quot;</span>,<br>  <span class="hljs-string">&quot;receiverId&quot;</span>: <span class="hljs-string">&quot;&quot;</span>,<br>  <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;&quot;</span>,<br>  <span class="hljs-string">&quot;sendTime&quot;</span>: <span class="hljs-string">&quot;&quot;</span><br>&#125;<br><span class="hljs-keyword">const</span> message = <span class="hljs-title function_">ref</span>(initMessage)<br><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">openSocket</span>(<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-title class_">WebSocket</span> == <span class="hljs-string">&quot;undefined&quot;</span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器不支持WebSocket&quot;</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器支持WebSocket&quot;</span>);<br>    <span class="hljs-comment">//指定要连接的服务器地址与端口</span><br>    <span class="hljs-keyword">const</span> socketUrl = <span class="hljs-string">`ws://localhost:8081/api/websocket/<span class="hljs-subst">$&#123;currentUserId&#125;</span>`</span>;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(socketUrl);<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(currentUser);<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(currentUserId);<br>    <span class="hljs-keyword">if</span> (socket != <span class="hljs-literal">null</span>) &#123;<br>      socket.<span class="hljs-title function_">close</span>();<br>      socket = <span class="hljs-literal">null</span>;<br>    &#125;<br>    <span class="hljs-comment">// 实例化WebSocket对象，建立连接</span><br>    socket = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocket</span>(socketUrl);<br>    <span class="hljs-comment">//打开事件</span><br>    socket.<span class="hljs-property">onopen</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) &#123;<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;websocket已打开&quot;</span>);<br>      <span class="hljs-title function_">setMessage</span>(<span class="hljs-string">&quot;websocket已打开&quot;</span>);<br>    &#125;;<br>    <span class="hljs-comment">//获得消息事件</span><br>    socket.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">msg</span>) &#123;<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(msg.<span class="hljs-property">data</span>);<br>      <span class="hljs-comment">//消息进入后的处理逻辑</span><br>      <span class="hljs-title function_">setMessage</span>(<span class="hljs-string">&quot;服务端回应: &quot;</span> + msg.<span class="hljs-property">data</span>);<br>    &#125;;<br>    <span class="hljs-comment">//关闭事件</span><br>    socket.<span class="hljs-property">onclose</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) &#123;<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;websocket已关闭&quot;</span>);<br>      <span class="hljs-title function_">setMessage</span>(<span class="hljs-string">&quot;websocket已关闭&quot;</span>);<br>    &#125;;<br>    <span class="hljs-comment">//发生了错误事件</span><br>    socket.<span class="hljs-property">onerror</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) &#123;<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;websocket发生了错误&quot;</span>);<br>    &#125;;<br>  &#125;<br>&#125;<br><br><span class="hljs-comment">// 发送消息</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">sendMessage</span>(<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-title class_">WebSocket</span> == <span class="hljs-string">&quot;undefined&quot;</span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器不支持WebSocket&quot;</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器支持WebSocket&quot;</span>);<br><br>    message.<span class="hljs-property">value</span> = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(&#123;<br>      <span class="hljs-attr">senderId</span>: currentUserId,<br>      <span class="hljs-attr">receiverId</span>: <span class="hljs-string">&quot;1657284893320364034&quot;</span>,<br>      <span class="hljs-attr">content</span>: <span class="hljs-string">&quot;你好&quot;</span>,<br>      <span class="hljs-attr">sendTime</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>()<br>    &#125;);<br><br>    <span class="hljs-keyword">if</span> (socket == <span class="hljs-literal">null</span>) &#123;<br>      retMes.<span class="hljs-property">value</span> += <span class="hljs-string">&quot;服务未开启&quot;</span><br>      <span class="hljs-keyword">return</span>;<br>    &#125;<br>    socket.<span class="hljs-title function_">send</span>(message.<span class="hljs-property">value</span>);<br>    <span class="hljs-title function_">setMessage</span>(message.<span class="hljs-property">value</span>);<br>  &#125;<br>&#125;<br><br><span class="hljs-comment">//将消息显示在网页上</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">setMessage</span>(<span class="hljs-params">message</span>) &#123;<br>  retMes.<span class="hljs-property">value</span> += message + <span class="hljs-string">&quot;\n&quot;</span>;<br>&#125;<br><br><span class="hljs-comment">//关闭WebSocket连接</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">closeWebSocket</span>(<span class="hljs-params"></span>) &#123;<br>  socket.<span class="hljs-title function_">close</span>();<br>&#125;<br><br><span class="hljs-comment">//监听窗口关闭事件，当窗口关闭时，主动去关闭websocket连接，防止连接还没断开就关闭窗口，server端会抛异常。</span><br><span class="hljs-variable language_">window</span>.<span class="hljs-property">onbeforeunload</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-title function_">closeWebSocket</span>();<br>&#125;;<br>&lt;/script&gt;<br></code></pre></td></tr></table></figure>

<h2 id="快速测试"><a href="#快速测试" class="headerlink" title="快速测试"></a>快速测试</h2><ul>
<li><h5 id="启动前后端项目，页面效果如下："><a href="#启动前后端项目，页面效果如下：" class="headerlink" title="启动前后端项目，页面效果如下："></a>启动前后端项目，页面效果如下：</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230913202912901.png" alt="image-20230913202912901"></p>
<ul>
<li><h5 id="操作步骤：连接socket-gt-发送消息"><a href="#操作步骤：连接socket-gt-发送消息" class="headerlink" title="操作步骤：连接socket -&gt; 发送消息"></a>操作步骤：连接socket -&gt; 发送消息</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230913202949188.png" alt="image-20230913202949188"></p>
<ul>
<li><h5 id="服务器接收到消息，记录连接的用户id，处理消息，并转发消息到指定用户id"><a href="#服务器接收到消息，记录连接的用户id，处理消息，并转发消息到指定用户id" class="headerlink" title="服务器接收到消息，记录连接的用户id，处理消息，并转发消息到指定用户id"></a>服务器接收到消息，记录连接的用户id，处理消息，并转发消息到指定用户id</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 收到客户端消息后调用的方法</span><br><span class="hljs-comment">    *</span><br><span class="hljs-comment">    * @ Param message 客户端发送过来的消息</span><br><span class="hljs-comment">    */</span><br>   <span class="hljs-meta">@OnMessage</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(String message, Session session)</span> &#123;<br>       log.info(<span class="hljs-string">&quot;收到来自窗口&quot;</span> + sid + <span class="hljs-string">&quot;的信息:&quot;</span> + message);<br>       <span class="hljs-comment">// 将 JSON 字符串 转成 Java 对象</span><br>       <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>       <span class="hljs-type">Message</span> <span class="hljs-variable">newMessage</span> <span class="hljs-operator">=</span> gson.fromJson(message, Message.class);<br><br>       <span class="hljs-type">Long</span> <span class="hljs-variable">senderId</span> <span class="hljs-operator">=</span> newMessage.getSenderId();<br>       <span class="hljs-type">Long</span> <span class="hljs-variable">receiverId</span> <span class="hljs-operator">=</span> newMessage.getReceiverId();<br>       <span class="hljs-comment">// 群发消息</span><br>       <span class="hljs-comment">// 存储信息到Redis 30min</span><br>       ValueOperations&lt;String, Object&gt; ops = redisTemplate.opsForValue();<br>       ops.set(<span class="hljs-string">&quot;memory:user:message:&quot;</span> + senderId, message, <span class="hljs-number">30</span>, TimeUnit.MINUTES);<br>       ops.set(<span class="hljs-string">&quot;memory:user:message:&quot;</span> + receiverId, message, <span class="hljs-number">30</span>, TimeUnit.MINUTES);<br>       <span class="hljs-keyword">for</span> (WebSocketServer item : webSocketSet) &#123;<br>           <span class="hljs-keyword">try</span> &#123;<br>               item.sendMessage(message);<br>           &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>               e.printStackTrace();<br>           &#125;<br>       &#125;<br>   &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="前端获取到服务器转发的消息，展示在页面（2023-09-13晚）"><a href="#前端获取到服务器转发的消息，展示在页面（2023-09-13晚）" class="headerlink" title="前端获取到服务器转发的消息，展示在页面（2023/09/13晚）"></a>前端获取到服务器转发的消息，展示在页面（2023/09/13晚）</h5></li>
</ul>
<h2 id="实时双向通信（Version2-0）"><a href="#实时双向通信（Version2-0）" class="headerlink" title="实时双向通信（Version2.0）"></a>实时双向通信（Version2.0）</h2><h3 id="业务流程梳理"><a href="#业务流程梳理" class="headerlink" title="业务流程梳理"></a>业务流程梳理</h3><ul>
<li><p>实现<strong>双方实时通信</strong>的基础是什么？宏观的来讲，当然是<strong>通信双方之间建立一条连接</strong>，用来收发数据</p>
<ul>
<li>如何建立这条连接呢？客户端之间建立连接一定是要把服务器作为中转站，让服务器负责连接两端</li>
</ul>
</li>
<li><p>建立连接之后，双方就可以发送消息了，服务器会帮我们搞定消息从哪里来、发送到哪里去</p>
<ul>
<li>这些消息的内容一定得包含这些字段：<strong>发送者、接收者、消息内容、发送时间</strong>等</li>
<li>这样，服务器就能知晓这些<strong>消息的来源和目的地</strong>了，能够准确地将消息转发到接收者</li>
</ul>
</li>
<li><p>收发过程中的这么多消息，应该存放在哪里呢？存放在数据库，我们这里建议<strong>使用Redis存储</strong></p>
<ul>
<li>这样，聊天产生的消息都会存进<strong>Redis</strong>中</li>
<li>当用户打开聊天界面时，<strong>加载双方的聊天记录</strong></li>
<li>当用户发送消息时，服务器负责精确<strong>转发</strong>给对方</li>
<li>用户成功发送消息有反馈，用户接收到消息也有反馈</li>
</ul>
</li>
<li><h5 id="这就是实时双向通信的整个业务流程（2023-09-25早）"><a href="#这就是实时双向通信的整个业务流程（2023-09-25早）" class="headerlink" title="这就是实时双向通信的整个业务流程（2023/09/25早）"></a>这就是实时双向通信的整个业务流程（2023/09/25早）</h5></li>
</ul>
<h4 id="做好简单配置"><a href="#做好简单配置" class="headerlink" title="做好简单配置"></a>做好简单配置</h4><ul>
<li><h5 id="导入-WebSocket-依赖坐标：（2023-09-30午）"><a href="#导入-WebSocket-依赖坐标：（2023-09-30午）" class="headerlink" title="导入 WebSocket 依赖坐标：（2023/09/30午）"></a>导入 WebSocket 依赖坐标：<strong>（2023/09/30午）</strong></h5></li>
</ul>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>         <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>         <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-websocket<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>     <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="编写配置类-WebSocketConfig："><a href="#编写配置类-WebSocketConfig：" class="headerlink" title="编写配置类 WebSocketConfig："></a>编写配置类 WebSocketConfig：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WebSocketConfig</span> &#123;<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> ServerEndpointExporter <span class="hljs-title function_">serverEndpointExporter</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ServerEndpointExporter</span>();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h4 id="向服务端发送消息"><a href="#向服务端发送消息" class="headerlink" title="向服务端发送消息"></a>向服务端发送消息</h4><ul>
<li><h5 id="我们来到前端Vue页面，编辑消息："><a href="#我们来到前端Vue页面，编辑消息：" class="headerlink" title="我们来到前端Vue页面，编辑消息："></a>我们来到前端Vue页面，编辑消息：</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs ts">&lt;div&gt;<br>  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">a-input</span> <span class="hljs-attr">v-model:value</span>=<span class="hljs-string">&quot;mesInput&quot;</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&quot;Basic usage&quot;</span> <span class="hljs-attr">size</span>=<span class="hljs-string">&quot;large&quot;</span> <span class="hljs-attr">style</span>=<span class="hljs-string">&quot;width: 90%&quot;</span></span></span><br><span class="hljs-tag"><span class="language-xml">       <span class="hljs-attr">showCount</span> <span class="hljs-attr">:maxlength</span>=<span class="hljs-string">&quot;300&quot;</span>/&gt;</span></span><br>  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">a-button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">&quot;sendMessage&quot;</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;primary&quot;</span> <span class="hljs-attr">size</span>=<span class="hljs-string">&quot;large&quot;</span> <span class="hljs-attr">style</span>=<span class="hljs-string">&quot;margin-left: 20px&quot;</span>&gt;</span></span><br><span class="language-xml">    发送消息</span><br><span class="language-xml">  <span class="hljs-tag">&lt;/<span class="hljs-name">a-button</span>&gt;</span></span><br>&lt;/div&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 消息输入</span><br><span class="hljs-keyword">const</span> mesInput = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&quot;&quot;</span>);<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="与后端服务器建立连接："><a href="#与后端服务器建立连接：" class="headerlink" title="与后端服务器建立连接："></a>与后端服务器建立连接：</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 主动连接</span><br><span class="hljs-title function_">openSocket</span>(currentUserId);<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">function</span> <span class="hljs-title function_">openSocket</span>(<span class="hljs-params">Id</span>) &#123;<br>  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-title class_">WebSocket</span> == <span class="hljs-string">&quot;undefined&quot;</span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器不支持WebSocket&quot;</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器支持WebSocket&quot;</span>);<br><br>    <span class="hljs-comment">//指定要连接的服务器地址与端口</span><br>    <span class="hljs-keyword">const</span> socketUrl = <span class="hljs-string">`ws://localhost:8081/api/websocket/<span class="hljs-subst">$&#123;Id&#125;</span>`</span>;<br>    <span class="hljs-keyword">if</span> (socket != <span class="hljs-literal">null</span>) &#123;<br>      socket.<span class="hljs-title function_">close</span>();<br>      socket = <span class="hljs-literal">null</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// 实例化WebSocket对象，建立连接</span><br>    socket = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocket</span>(socketUrl);<br>    <br>    ........................<br>    <br>  &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="服务器监听是否有新的连接建立："><a href="#服务器监听是否有新的连接建立：" class="headerlink" title="服务器监听是否有新的连接建立："></a>服务器监听是否有新的连接建立：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 连接建立成功调用的方法</span><br><span class="hljs-comment">    */</span><br>   <span class="hljs-meta">@OnOpen</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onOpen</span><span class="hljs-params">(Session session, <span class="hljs-meta">@PathParam(&quot;sid&quot;)</span> String sid)</span> &#123;<br>       <span class="hljs-built_in">this</span>.session = session;<br>       webSocketSet.add(<span class="hljs-built_in">this</span>); <span class="hljs-comment">//加入set中</span><br>       <span class="hljs-built_in">this</span>.sid = sid;<br>       addOnlineCount(); <span class="hljs-comment">//在线数加1</span><br>       <span class="hljs-keyword">try</span> &#123;<br>           sendMessage(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>().toJson(<span class="hljs-string">&quot;连接成功&quot;</span>));<br>           log.info(<span class="hljs-string">&quot;有新窗口开始监听:&quot;</span> + sid + <span class="hljs-string">&quot;,当前在线人数为:&quot;</span> + getOnlineCount());<br>       &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>           log.error(<span class="hljs-string">&quot;websocket IO Exception&quot;</span>);<br>       &#125;<br>   &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="点击发送消息，发送消息至指定端口，并实时更新聊天记录：（2023-09-30午）"><a href="#点击发送消息，发送消息至指定端口，并实时更新聊天记录：（2023-09-30午）" class="headerlink" title="点击发送消息，发送消息至指定端口，并实时更新聊天记录：（2023/09/30午）"></a>点击发送消息，发送消息至指定端口，并实时更新聊天记录：<strong>（2023/09/30午）</strong></h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 发送消息</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">sendMessage</span>(<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-title class_">WebSocket</span> == <span class="hljs-string">&quot;undefined&quot;</span>) &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器不支持WebSocket&quot;</span>);<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;您的浏览器支持WebSocket&quot;</span>);<br><br>    <span class="hljs-comment">// 在需要校验的地方使用 isMesInputEmpty</span><br>    <span class="hljs-keyword">if</span> (isMesInputEmpty.<span class="hljs-property">value</span>) &#123;<br>      message.<span class="hljs-title function_">warning</span>(<span class="hljs-string">&quot;发送消息不能为空&quot;</span>)<br>      <span class="hljs-keyword">return</span>;<br>    &#125;<br>	<br>    sendMsg.<span class="hljs-property">value</span> = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(&#123;<br>      <span class="hljs-attr">senderId</span>: currentUserId,<br>      <span class="hljs-attr">receiverId</span>: chatUser.<span class="hljs-property">value</span>,<br>      <span class="hljs-attr">content</span>: mesInput.<span class="hljs-property">value</span>,<br>      <span class="hljs-attr">sendTime</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>()<br>    &#125;);<br>    <span class="hljs-comment">// 校验是否连接服务器</span><br>    <span class="hljs-keyword">if</span> (socket == <span class="hljs-literal">null</span>) &#123;<br>      retMes.<span class="hljs-property">value</span> += <span class="hljs-string">&quot;服务未开启&quot;</span><br>      <span class="hljs-keyword">return</span>;<br>    &#125;<br>    <span class="hljs-comment">// 发送消息</span><br>    socket.<span class="hljs-title function_">send</span>(sendMsg.<span class="hljs-property">value</span>);<br>    <span class="hljs-comment">// setMessage(sendMsg.value);</span><br>    <span class="hljs-comment">// 实时更新聊天记录</span><br>    <span class="hljs-title function_">getMesList</span>(chatUser.<span class="hljs-property">value</span>);<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功发送一条消息&quot;</span>)<br>    <span class="hljs-comment">// 清空输入框内编辑的消息</span><br>    mesInput.<span class="hljs-property">value</span> = <span class="hljs-string">&quot;&quot;</span>;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h4 id="服务端转发消息"><a href="#服务端转发消息" class="headerlink" title="服务端转发消息"></a>服务端转发消息</h4><ul>
<li>服务端接受到发送的消息：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@ServerEndpoint(&quot;/websocket/&#123;sid&#125;&quot;)</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WebSocketServer</span> &#123;<br>  <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 接受客户端发来的消息</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> message 消息</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@throws</span> IOException IOException</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@OnMessage</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onMessage</span><span class="hljs-params">(String message)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>        log.info(<span class="hljs-string">&quot;收到来自窗口&quot;</span> + sid + <span class="hljs-string">&quot;的信息:&quot;</span> + message);<br>        <span class="hljs-comment">// 1.消息存放Redis</span><br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">saveMessage</span> <span class="hljs-operator">=</span> saveMessage(message);<br>        <span class="hljs-comment">// 2.校验存放</span><br>        <span class="hljs-keyword">if</span> (!saveMessage) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.UPDATE_ERROR_REDIS, <span class="hljs-string">&quot;用户信息存放失败&quot;</span>);<br>        &#125;<br>        <span class="hljs-comment">// 3.消息转发</span><br>        msgForward(message, webSocketSet, sid);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>接收到客户端消息，需要进行如下操作：</p>
<ul>
<li><h5 id="持久化该消息至数据库中，即保存为聊天记录"><a href="#持久化该消息至数据库中，即保存为聊天记录" class="headerlink" title="持久化该消息至数据库中，即保存为聊天记录"></a>持久化该消息至数据库中，即保存为聊天记录</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Redis 负责存储离线消息</span><br>   <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> RedisTemplate&lt;String, Object&gt; redisTemplate = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RedisTemplate</span>&lt;&gt;();<br>  <br>   <span class="hljs-meta">@Resource</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setRedisTemplate</span><span class="hljs-params">(RedisTemplate redisTemplate)</span> &#123;<br>       WebSocketServer.redisTemplate = redisTemplate;<br>   &#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java">  <span class="hljs-comment">/**</span><br><span class="hljs-comment">   * 存放消息到Redis 用户id -&gt; (key: UUID, value: message)</span><br><span class="hljs-comment">   *</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@param</span> message 消息</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@return</span> 存放成功与否</span><br><span class="hljs-comment">   */</span><br>  <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">saveMessage</span><span class="hljs-params">(String message)</span> &#123;<br>      <span class="hljs-comment">// 3.存储信息到Redis 30min</span><br>      <span class="hljs-comment">// 3.1.设置key</span><br>      <span class="hljs-type">String</span> <span class="hljs-variable">msgKey</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;memory:user:message:&quot;</span>;<br>      <span class="hljs-comment">// 3.2.获取发送者 接收者</span><br>      <br>...................................<br>  <br>      <span class="hljs-comment">// 3.3.存放message</span><br>      <span class="hljs-keyword">try</span> &#123;<br>          HashOperations&lt;String, Object, Object&gt; opsForHash = redisTemplate.opsForHash();<br>          opsForHash.put(senderMsgKey, generateMessageId(), message);<br>          opsForHash.put(receiverMsgKey, generateMessageId(), message);<br>          <span class="hljs-comment">// 3.3.设置键的过期时间，单位为h</span><br>          <span class="hljs-type">long</span> <span class="hljs-variable">expireTime</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>; <span class="hljs-comment">// 设置为2hour</span><br>          redisTemplate.expire(senderMsgKey, expireTime, TimeUnit.HOURS);<br>          redisTemplate.expire(receiverMsgKey, expireTime, TimeUnit.HOURS);<br>      &#125; <br>      ...................................<br>  &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="查看消息内容，判断消息来源-senderId，确认转发对象-receiverId，实现精确转发"><a href="#查看消息内容，判断消息来源-senderId，确认转发对象-receiverId，实现精确转发" class="headerlink" title="查看消息内容，判断消息来源 senderId，确认转发对象 receiverId，实现精确转发"></a>查看消息内容，判断消息来源 senderId，确认转发对象 receiverId，实现精确转发</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">   * 消息转发</span><br><span class="hljs-comment">   *</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@param</span> message      消息</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@param</span> webSocketSet webSocketSet</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@param</span> sid          转发用户id</span><br><span class="hljs-comment">   */</span><br>  <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">msgForward</span><span class="hljs-params">(String message, CopyOnWriteArraySet&lt;WebSocketServer&gt; webSocketSet, String sid)</span> &#123;<br>      <span class="hljs-keyword">for</span> (WebSocketServer item : webSocketSet) &#123;<br>          <span class="hljs-keyword">try</span> &#123;<br>              <span class="hljs-comment">//这里可以设定只推送给这个sid的，为null则全部推送</span><br>              <span class="hljs-keyword">if</span> (item.sid.equals(sid)) &#123;<br>                  item.sendMessage(message);<br>              &#125; <span class="hljs-keyword">else</span> &#123;<br>                  item.sendMessage(message);<br>              &#125;<br>          &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>              e.printStackTrace();<br>          &#125;<br>      &#125;<br>  &#125;<br></code></pre></td></tr></table></figure></li>
</ul>
<h4 id="客户端监听"><a href="#客户端监听" class="headerlink" title="客户端监听"></a>客户端监听</h4><ul>
<li><h5 id="客户端监听到服务端主动发送的消息，判断该消息的接收者"><a href="#客户端监听到服务端主动发送的消息，判断该消息的接收者" class="headerlink" title="客户端监听到服务端主动发送的消息，判断该消息的接收者"></a>客户端监听到服务端主动发送的消息，判断该消息的接收者</h5><ul>
<li>如果是该消息接收者，则拿到该消息，否则，不予理会</li>
</ul>
</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">//获得消息事件(获得服务端转发的消息)</span><br>  socket.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">msg</span>) &#123;<br>    <span class="hljs-comment">// 实时更新聊天记录</span><br>    <span class="hljs-title function_">getMesList</span>(chatUser.<span class="hljs-property">value</span>);<br>    receiveMsg.<span class="hljs-property">value</span> = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(msg.<span class="hljs-property">data</span>)<br>    <span class="hljs-comment">// 是发给自己的消息 更新聊天记录</span><br>    <span class="hljs-keyword">if</span> (currentUserId === receiveMsg.<span class="hljs-property">value</span>.<span class="hljs-property">receiverId</span>) &#123;<br>      <span class="hljs-title function_">openNotification</span>()<br>      <span class="hljs-comment">// 刷新聊天记录</span><br>      <span class="hljs-title function_">getMesList</span>(chatUser.<span class="hljs-property">value</span>);<br>    &#125;<br>  &#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="至此，整个实时双向通信流程已介绍完毕"><a href="#至此，整个实时双向通信流程已介绍完毕" class="headerlink" title="至此，整个实时双向通信流程已介绍完毕"></a>至此，整个实时双向通信流程已介绍完毕</h5></li>
</ul>
<h3 id="编码实现重难点"><a href="#编码实现重难点" class="headerlink" title="编码实现重难点"></a>编码实现重难点</h3><h4 id="封装消息内容"><a href="#封装消息内容" class="headerlink" title="封装消息内容"></a>封装消息内容</h4><ul>
<li><p>客户端之间的通信是经由服务器的，那么通信时手法的消息报文应该包含哪些信息呢？<strong>（2023/09/30午）</strong></p>
<ul>
<li><h5 id="发送者、接收者、消息内容、发送时间"><a href="#发送者、接收者、消息内容、发送时间" class="headerlink" title="发送者、接收者、消息内容、发送时间"></a>发送者、接收者、消息内容、发送时间</h5></li>
</ul>
</li>
<li><p>我们简单地封装一个消息类即可：</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Message</span> &#123;<br>    <span class="hljs-keyword">private</span> Long senderId;<br>    <span class="hljs-keyword">private</span> Long receiverId;<br>    <span class="hljs-keyword">private</span> String content;<br><br>    <span class="hljs-meta">@JsonFormat(pattern = &quot;yyyy-MM-dd HH:mm:ss&quot;, timezone = &quot;GMT+8&quot;)</span><br>    <span class="hljs-keyword">private</span> Date sendTime;<br>&#125;<br></code></pre></td></tr></table></figure>

<h4 id="消息存放方式"><a href="#消息存放方式" class="headerlink" title="消息存放方式"></a>消息存放方式</h4><ul>
<li><p>我们使用 Redis 存放消息，考虑下消息的淘汰策略，简单地制定为仅保存48h之内的消息</p>
<ul>
<li>用户发送消息（<strong>message</strong>），消息中携带的内容：发送者id，目标id，消息内容，发送时间</li>
<li>服务器收到消息，按以发送者id为key值，<strong>将 message 存储至Redis中</strong></li>
</ul>
</li>
<li><p>我们选用 Hash 数据结构存放聊天消息：</p>
<ul>
<li>每个用户发送的消息，都保存在自己的 key 下，即 <strong>“memory:user:message: 用户id”</strong></li>
<li>在每一个用户下，存放他具体发送过的消息，我们<strong>随机生成ID作为消息的key</strong>，value值即为之前<strong>封装过的消息类Message</strong></li>
</ul>
</li>
<li><p>生成随机消息ID，作为key</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 生成消息id</span><br><span class="hljs-comment">    *</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@return</span> 消息id</span><br><span class="hljs-comment">    */</span><br>   <span class="hljs-keyword">private</span> String <span class="hljs-title function_">generateMessageId</span><span class="hljs-params">()</span> &#123;<br>       <span class="hljs-comment">// 随机生成一个唯一的消息ID</span><br>       <span class="hljs-type">UUID</span> <span class="hljs-variable">messageId</span> <span class="hljs-operator">=</span> UUID.randomUUID();<br>       <span class="hljs-keyword">return</span> messageId.toString();<br>   &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>存放消息，每个用户下都保存了他发送过的消息记录：</p>
<ul>
<li><h5 id="发送者id即为该用户id"><a href="#发送者id即为该用户id" class="headerlink" title="发送者id即为该用户id"></a>发送者id即为该用户id</h5></li>
<li><h5 id="接收者id、消息内容、发送时间"><a href="#接收者id、消息内容、发送时间" class="headerlink" title="接收者id、消息内容、发送时间"></a>接收者id、消息内容、发送时间</h5></li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">HashOperations&lt;String, Object, Object&gt; opsForHash = redisTemplate.opsForHash();<br>         opsForHash.put(senderMsgKey, generateMessageId(), message);<br>         opsForHash.put(receiverMsgKey, generateMessageId(), message);<br>         <span class="hljs-comment">// 3.3.设置键的过期时间，单位为h</span><br>         <span class="hljs-type">long</span> <span class="hljs-variable">expireTime</span> <span class="hljs-operator">=</span> <span class="hljs-number">48</span>; <span class="hljs-comment">// 设置为48hour</span><br>         redisTemplate.expire(senderMsgKey, expireTime, TimeUnit.HOURS);<br>         redisTemplate.expire(receiverMsgKey, expireTime, TimeUnit.HOURS);<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="这就是存放在Redis中的消息："><a href="#这就是存放在Redis中的消息：" class="headerlink" title="这就是存放在Redis中的消息："></a>这就是存放在Redis中的消息：</h5></li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230930120838610.png" alt="image-20230930120838610"></p>
<p>消息存放完成后，服务器再再转发 message 到客户端，所有客户端都会获取到消息提示</p>
<ul>
<li>仅需<strong>判断消息的 receiverId是否为自己的id</strong>，选择<strong>是否接收该消息</strong>，并实时同步聊天记录</li>
</ul>
<h4 id="实时同步聊天记录"><a href="#实时同步聊天记录" class="headerlink" title="实时同步聊天记录"></a>实时同步聊天记录</h4><ul>
<li>判断出消息的接收者为自己后，才会会进行下一步操作：<strong>实时获取最新的聊天记录</strong>** <strong>（2023/09/30午）</strong></li>
<li>那么<strong>获取聊天记录的业务逻辑</strong>又应该是怎样的呢？</li>
</ul>
<h4 id="获取聊天记录"><a href="#获取聊天记录" class="headerlink" title="获取聊天记录"></a>获取聊天记录</h4><ul>
<li><p>我们一直谈到获取聊天记录，那应该获取那些聊天记录呢？</p>
</li>
<li><p>在现在的好友私聊中，双方收发的消息报文里面，包含了发送者 senderId 和接收者 receiverId</p>
</li>
<li><p>那么就应该使用收发双方的id</p>
<ul>
<li>根据 senderId，分别获取到给对方发送过的消息记录</li>
<li>将两个消息记录/消息列表合并，按发送时间 sendTime 升序排序，最后返回</li>
<li>这样就拿到了两人的聊天记录了</li>
</ul>
</li>
<li><h5 id="具体代码如下："><a href="#具体代码如下：" class="headerlink" title="具体代码如下："></a>具体代码如下：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 获取聊天消息记录</span><br><span class="hljs-comment">    *</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@param</span> senderId   发送者</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@param</span> receiverId 接收者</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@param</span> request    request</span><br><span class="hljs-comment">    * <span class="hljs-doctag">@return</span> 聊天记录</span><br><span class="hljs-comment">    */</span><br>   <span class="hljs-meta">@Override</span><br>   <span class="hljs-keyword">public</span> List&lt;Message&gt; <span class="hljs-title function_">listMessage</span><span class="hljs-params">(Long senderId, Long receiverId, HttpServletRequest request)</span> &#123;<br>       <span class="hljs-comment">// 1.获取各自发送给对方的的消息记录</span><br>       List&lt;Message&gt; message1 = getMessage(senderId, receiverId);<br>       List&lt;Message&gt; message2 = getMessage(receiverId, senderId);<br>       <span class="hljs-comment">// 2.合并双方消息记录</span><br>       List&lt;Message&gt; messageList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>       messageList.addAll(message1);<br>       messageList.addAll(message2);<br><br>       <span class="hljs-comment">// 3.按发送时间sendTime, 排列</span><br>       messageList.sort(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Comparator</span>&lt;Message&gt;() &#123;<br>           <span class="hljs-meta">@Override</span><br>           <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">compare</span><span class="hljs-params">(Message o1, Message o2)</span> &#123;<br>               <span class="hljs-keyword">return</span> o1.getSendTime().compareTo(o2.getSendTime());<br>           &#125;<br>       &#125;);<br><br>       <span class="hljs-keyword">return</span> messageList;<br>   &#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 获取消息</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> senderId   发送者</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> receiverId 接收者</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 聊天记录</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> List&lt;Message&gt; <span class="hljs-title function_">getMessage</span><span class="hljs-params">(Long senderId, Long receiverId)</span> &#123;<br>        Map&lt;Object, Object&gt; mesEntriesJson = redisTemplate.opsForHash().entries(USER_CHAT_MESSAGE + senderId);<br><br>        <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>        <span class="hljs-type">String</span> <span class="hljs-variable">jsonString</span> <span class="hljs-operator">=</span> gson.toJson(mesEntriesJson);<br>        <span class="hljs-comment">// 1.以当前登录用户id作为key，获取聊天消息记录</span><br>        Map&lt;Object, Object&gt; mesEntries = gson.fromJson(jsonString, <span class="hljs-keyword">new</span> <span class="hljs-title class_">TypeToken</span>&lt;Map&lt;Object, Object&gt;&gt;() &#123;<br>        &#125;.getType());<br>        <span class="hljs-comment">// 判空 消息记录为空</span><br>        <span class="hljs-keyword">if</span> (mesEntries == <span class="hljs-literal">null</span>)<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br><br>        <span class="hljs-comment">// 2.获取value值 - message</span><br>        ArrayList&lt;Message&gt; messageList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>        Collection&lt;Object&gt; mesValuesJson = mesEntries.values();<br>        <span class="hljs-keyword">for</span> (Object mesValueJson : mesValuesJson) &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">mesValuesJsonStr</span> <span class="hljs-operator">=</span> (String) mesValueJson;<br>            <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> gson.fromJson(mesValuesJsonStr, Message.class);<br>            <span class="hljs-comment">// 获取与receiverId通信的消息</span><br>            <span class="hljs-keyword">if</span> (message.getReceiverId().equals(receiverId)) &#123;<br>                messageList.add(message);<br>            &#125;<br>        &#125;<br><br>        <span class="hljs-keyword">return</span> messageList;<br>    &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="这样就拿到了：你发给我的消息、我发给你的消息"><a href="#这样就拿到了：你发给我的消息、我发给你的消息" class="headerlink" title="这样就拿到了：你发给我的消息、我发给你的消息"></a>这样就拿到了：你发给我的消息、我发给你的消息</h5></li>
</ul>
<h4 id="实现点击进入对应好友聊天窗口"><a href="#实现点击进入对应好友聊天窗口" class="headerlink" title="实现点击进入对应好友聊天窗口"></a>实现点击进入对应好友聊天窗口</h4><ul>
<li>在好友私聊页面，我们点击私聊，就会携带该该好友id，跳转至聊天大厅</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;template #actions&gt;<br>                 &lt;a-button size=&quot;large&quot; type=&quot;primary&quot; ghost @click=&quot;goToTab(item)&quot;&gt;私聊&lt;/a-button&gt;<br>               &lt;/template&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 监听页面变化</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">goToTab</span> = (<span class="hljs-params">item: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  router.<span class="hljs-title function_">push</span>(&#123;<br>    <span class="hljs-attr">name</span>: <span class="hljs-string">&quot;chat&quot;</span>,<br>    <span class="hljs-attr">path</span>: <span class="hljs-string">&quot;/chat&quot;</span>,<br>    <span class="hljs-attr">query</span>: &#123;<br>      <span class="hljs-attr">chatTabName</span>: item.<span class="hljs-property">id</span>,<br>    &#125;<br>  &#125;)<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>在聊天大厅处，我们<strong>随时监听聊天窗口 activeKey 的值的变化</strong>：<strong>（2023/09/30午）</strong></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 监听 activeKey 的变化，更新存储中的值</span><br><span class="hljs-title function_">watch</span>(activeKey, <span class="hljs-function">(<span class="hljs-params">value</span>) =&gt;</span> &#123;<br>  <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">setItem</span>(<span class="hljs-string">&#x27;activeKey&#x27;</span>, value);<br>&#125;);<br></code></pre></td></tr></table></figure>

<ul>
<li><p>（这里的内容详情，可在 <strong>前端框架踩坑与技巧总结：Ant Design、Vite，助您无忧编程</strong> 的 <strong>监听选中的Tab标签页</strong> 一栏中学习了解） </p>
</li>
<li><h5 id="而首次进入聊天大厅，我们会定位-tab页到对应私聊好友的聊天窗口处"><a href="#而首次进入聊天大厅，我们会定位-tab页到对应私聊好友的聊天窗口处" class="headerlink" title="而首次进入聊天大厅，我们会定位 tab页到对应私聊好友的聊天窗口处"></a>而首次进入聊天大厅，我们会定位 tab页到对应私聊好友的聊天窗口处</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 记忆选中的Tab标签页</span><br><span class="hljs-keyword">if</span> (chatTabName.<span class="hljs-property">value</span> === <span class="hljs-literal">undefined</span>) &#123;<br>  activeKey.<span class="hljs-property">value</span> = <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">&#x27;activeKey&#x27;</span>)<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>  activeKey.<span class="hljs-property">value</span> = chatTabName.<span class="hljs-property">value</span>.<span class="hljs-property">chatTabName</span>;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230930123901082.png" alt="image-20230930123901082"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230930124124027.png" alt="image-20230930124124027"></p>
<ul>
<li><h5 id="同时也保证了在刷新页面后，仍然选中之前选中的-tab页聊天窗口（2023-09-30午）"><a href="#同时也保证了在刷新页面后，仍然选中之前选中的-tab页聊天窗口（2023-09-30午）" class="headerlink" title="同时也保证了在刷新页面后，仍然选中之前选中的 tab页聊天窗口（2023/09/30午）"></a>同时也保证了在刷新页面后，仍然选中之前选中的 tab页聊天窗口（2023/09/30午）</h5></li>
<li><h5 id="如果是从用户中心或者博文社区直接进入聊天大厅，则默认选中文件传输助手聊天窗口，极大提升用户体验"><a href="#如果是从用户中心或者博文社区直接进入聊天大厅，则默认选中文件传输助手聊天窗口，极大提升用户体验" class="headerlink" title="如果是从用户中心或者博文社区直接进入聊天大厅，则默认选中文件传输助手聊天窗口，极大提升用户体验"></a>如果是从用户中心或者博文社区直接进入聊天大厅，则默认选中文件传输助手聊天窗口，极大提升用户体验</h5></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// tab页 key</span><br><span class="hljs-keyword">const</span> activeKey = <span class="hljs-title function_">ref</span>(currentUserId);<br></code></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;!--文件传输助手--&gt;<br>        &lt;a-tab-pane v-model:activeKey=&quot;activeKey&quot; :key=&quot;currentUserId&quot; tab=&quot;文件传输助手&quot;<br>                    @click=&quot;handleTabChange&quot;&gt;<br>          &lt;div class=&quot;msgWindow&quot;&gt;<br>            &lt;h1&gt;收发文件，您的得力助手&lt;/h1&gt;<br>          &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230930123819934.png" alt="image-20230930123819934"></p>
<h2 id="踩坑记录"><a href="#踩坑记录" class="headerlink" title="踩坑记录"></a>踩坑记录</h2><h4 id="RedisTemplate注入失败"><a href="#RedisTemplate注入失败" class="headerlink" title="RedisTemplate注入失败"></a>RedisTemplate注入失败</h4><ul>
<li><p>这绝对是我踩过最令人抓狂的坑了，入上述代码所示，在@onMessage方法中获取到用户发来的消息后，服务器需要处理消息</p>
</li>
<li><p>我的处理方法是：<strong>以发送者 senderId 和接收者 receiverId 为key，分别将消息存储到 Redis 中，设置过期时间</strong></p>
</li>
<li><p>这里就遇到了这个问题：<strong>RedisTemplate注入失败</strong></p>
</li>
<li><p>我的初始代码如下：</p>
</li>
</ul>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-meta">@Resource</span><br>   <span class="hljs-keyword">private</span> <span class="hljs-title class_">RedisTemplate</span>&lt;<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Object</span>&gt; redisTemplate = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RedisTemplate</span>&lt;&gt;();<br></code></pre></td></tr></table></figure>

<figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cmake">// 存储信息到Redis <span class="hljs-number">30</span>min<br>ValueOperations&lt;<span class="hljs-keyword">String</span>, Object&gt; ops = redisTemplate.opsForValue();<br>ops.<span class="hljs-keyword">set</span>(<span class="hljs-string">&quot;memory:user:message:&quot;</span> + senderId, <span class="hljs-keyword">message</span>, <span class="hljs-number">30</span>, TimeUnit.MINUTES);<br>ops.<span class="hljs-keyword">set</span>(<span class="hljs-string">&quot;memory:user:message:&quot;</span> + receiverId, <span class="hljs-keyword">message</span>, <span class="hljs-number">30</span>, TimeUnit.MINUTES);<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="看起来是很正常的依赖注入，但是却报了如下错误：（2023-09-13晚）"><a href="#看起来是很正常的依赖注入，但是却报了如下错误：（2023-09-13晚）" class="headerlink" title="看起来是很正常的依赖注入，但是却报了如下错误：（2023/09/13晚）"></a>看起来是很正常的依赖注入，但是却报了如下错误：（2023/09/13晚）</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230913203806048.png" alt="image-20230913203806048"></p>
<ul>
<li><p>我确认Redis连接无误，项目中其他业务中，都能正常使用到 redisTemplate 执行缓存</p>
</li>
<li><h5 id="但唯独在WebSocket服务中却执行失败了：RedisTemplate-注入失败，为null"><a href="#但唯独在WebSocket服务中却执行失败了：RedisTemplate-注入失败，为null" class="headerlink" title="但唯独在WebSocket服务中却执行失败了：RedisTemplate 注入失败，为null"></a>但唯独在WebSocket服务中却执行失败了：RedisTemplate 注入失败，为null</h5></li>
<li><h5 id="这就是Springboot项目中WebSocket服务的特殊性了，可以参考这篇博客："><a href="#这就是Springboot项目中WebSocket服务的特殊性了，可以参考这篇博客：" class="headerlink" title="这就是Springboot项目中WebSocket服务的特殊性了，可以参考这篇博客："></a>这就是Springboot项目中WebSocket服务的特殊性了，可以参考这篇博客：</h5></li>
<li><h5 id="Springboot项目WebSocket服务中不能注入（-Autowired和-Resoure）报null错误-jeecgredisclient-localhostAND的博客-CSDN博客"><a href="#Springboot项目WebSocket服务中不能注入（-Autowired和-Resoure）报null错误-jeecgredisclient-localhostAND的博客-CSDN博客" class="headerlink" title="Springboot项目WebSocket服务中不能注入（@Autowired和@Resoure）报null错误_jeecgredisclient_localhostAND的博客-CSDN博客"></a><a target="_blank" rel="noopener" href="https://blog.csdn.net/blocalhost/article/details/125721825">Springboot项目WebSocket服务中不能注入（@Autowired和@Resoure）报null错误_jeecgredisclient_localhostAND的博客-CSDN博客</a></h5></li>
<li><h5 id="原因我在这里写出："><a href="#原因我在这里写出：" class="headerlink" title="原因我在这里写出："></a>原因我在这里写出：</h5></li>
</ul>
<figure class="highlight mel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs mel">本质原因：<span class="hljs-keyword">spring</span>管理的都是单例(singleton)，和 websocket(多对象)相冲突。<br><br>详细解释：项目启动时初始化，会初始化 websocket （非用户连接的），<span class="hljs-keyword">spring</span> 同时会为其注入 mapper，该对象的 mapper不是 null，被成功注入。但是，由于 <span class="hljs-keyword">spring</span> 默认管理的是单例，所以只会注入一次 mapper。当新用户进入聊天时，系统又会创建一个新的 websocket 对象，这时矛盾出现了：<span class="hljs-keyword">spring</span> 管理的都是单例，不会给第二个 websocket 对象注入 mapper，所以导致只要是用户连接创建的 websocket 对象，都不能再注入了。<br><br>像 controller 里面有 service， service 里面有 dao。因为 controller，service ，dao 都有是单例，所以注入时不会报 null。但是 websocket 不是单例，所以使用<span class="hljs-keyword">spring</span>注入一次后，后面的对象就不会再注入了，会报null。<br></code></pre></td></tr></table></figure>

<ul>
<li>我修改了注入的方法，代码如下：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Redis 负责存储离线消息</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> RedisTemplate&lt;String, Object&gt; redisTemplate = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RedisTemplate</span>&lt;&gt;();<br><br> <span class="hljs-meta">@Resource</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setRedisTemplate</span><span class="hljs-params">(RedisTemplate redisTemplate)</span> &#123;<br>     WebSocketServer.redisTemplate = redisTemplate;<br> &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="可以看到，本地-Redis-中已经成功地存储了用户消息，redisTemplate-注入成功，问题解决：（2023-09-13晚）"><a href="#可以看到，本地-Redis-中已经成功地存储了用户消息，redisTemplate-注入成功，问题解决：（2023-09-13晚）" class="headerlink" title="可以看到，本地 Redis 中已经成功地存储了用户消息，redisTemplate 注入成功，问题解决：（2023/09/13晚）"></a>可以看到，本地 Redis 中已经成功地存储了用户消息，redisTemplate 注入成功，问题解决：（2023/09/13晚）</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230913204727509.png" alt="image-20230913204727509"></p>
<ul>
<li><a target="_blank" rel="noopener" href="https://ask.csdn.net/questions/7667873">【报NullPointerException】springboot 中 websocket 的 @OnMessage 中使用自定义的工具类，报空指针异常_大数据-CSDN问答</a></li>
</ul>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1>
                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
    <div class="post-meta mr-3 d-flex align-items-center">
      <i class="iconfont icon-category"></i>
      

<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/categories/%E7%BB%8F%E9%AA%8C/" class="category-chain-item">经验</a>
  
  
    <span>></span>
    
  <a href="/categories/%E7%BB%8F%E9%AA%8C/SpringBoot/" class="category-chain-item">SpringBoot</a>
  
  
    <span>></span>
    
  <a href="/categories/%E7%BB%8F%E9%AA%8C/SpringBoot/WebSocket/" class="category-chain-item">WebSocket</a>
  
  
    <span>></span>
    
  <a href="/categories/%E7%BB%8F%E9%AA%8C/SpringBoot/WebSocket/%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/" class="category-chain-item">实时通信</a>
  
  

  

  

  

      </span>
    
  
</span>

    </div>
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/tags/%E7%BB%8F%E9%AA%8C/">#经验</a>
      
        <a href="/tags/SpringBoot/">#SpringBoot</a>
      
        <a href="/tags/WebSocket/">#WebSocket</a>
      
        <a href="/tags/%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/">#实时通信</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信</div>
      <div>http://example.com/2023/09/13/实战指南：使用 Spring Boot 集成 WebSocket 实现前后端实时通信/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>Memory</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2023年9月13日</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>更新于</div>
          <div>2023年9月30日</div>
        </div>
      
      
        <div class="license-meta-item">
          <div>许可协议</div>
          <div>
            
              
              
                <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
                  <span class="hint--top hint--rounded" aria-label="BY - 署名">
                    <i class="iconfont icon-by"></i>
                  </span>
                </a>
              
            
          </div>
        </div>
      
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/2023/09/17/%E6%94%B9%E5%8F%98%E7%BC%96%E7%A0%81%E6%B8%B8%E6%88%8F%E8%A7%84%E5%88%99%EF%BC%9A%E6%8F%AD%E7%A7%9823%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9A%84%E9%AD%85%E5%8A%9B%E5%92%8C%E5%AE%9E%E7%94%A8%E6%80%A7/" title="改变编码游戏规则：揭秘23种设计模式的魅力和实用性">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">改变编码游戏规则：揭秘23种设计模式的魅力和实用性</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2023/09/10/Memory%20%E7%BC%98%E5%BF%86%E4%BA%A4%E5%8F%8B%E7%A4%BE%E5%8C%BA-%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="Memory 缘忆交友社区-开发文档">
                        <span class="hidden-mobile">Memory 缘忆交友社区-开发文档</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
  
  
    <article id="comments">
      

    </article>
  


          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="margin-left: -1rem">
    <div id="toc">
  <p class="toc-header">
    <i class="iconfont icon-list"></i>
    <span>目录</span>
  </p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.fluid-dev.com/docs/guide/" target="_blank" rel="nofollow noopener"> <span>Memory | 个人文档站点</span> </a>
<i class="iconfont icon-love"></i> <a href="https://hexo.fluid-dev.com/docs/guide/" target="_blank" rel="nofollow noopener"> <span>配置指南 | Hexo Fluid 用户手册</span> </a>   
    </div>
  
  
    <div class="statistics">
  
  

  
    
      <span id="leancloud-site-pv-container" style="display: none">
        总访问量 
        <span id="leancloud-site-pv"></span>
         次
      </span>
    
    
      <span id="leancloud-site-uv-container" style="display: none">
        总访客数 
        <span id="leancloud-site-uv"></span>
         人
      </span>
    
    

  
</div>

  
  
    <!-- 备案信息 ICP for China -->
    <div class="beian">
  <span>
    <a href="http://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener">
      京ICP证123456号
    </a>
  </span>
  
    
      <span>
        <a
          href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=12345678"
          rel="nofollow noopener"
          class="beian-police"
          target="_blank"
        >
          
            <span style="visibility: hidden; width: 0">|</span>
            <img src="/img/police_beian.png" alt="police-icon"/>
          
          <span>京公网安备12345678号</span>
        </a>
      </span>
    
  
</div>

  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>







  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init(Object.assign({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      scrollSmooth    : true,
      includeTitleTags: true,
      headingsOffset  : -boardTop,
    }, CONFIG.toc));
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }

    Fluid.events.registerRefreshCallback(function() {
      if ('tocbot' in window) {
        tocbot.refresh();
        var toc = jQuery('#toc');
        if (toc.length === 0 || !tocbot) {
          return;
        }
        if (toc.find('.toc-list-item').length > 0) {
          toc.css('visibility', 'visible');
        }
      }
    });
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.11/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));

    Fluid.events.registerRefreshCallback(function() {
      if ('anchors' in window) {
        anchors.removeAll();
        var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
        var res = [];
        for (var item of el) {
          res.push('.markdown-body > ' + item.trim());
        }
        if (CONFIG.anchorjs.placement === 'left') {
          anchors.options.class = 'anchorjs-link-left';
        }
        anchors.add(res.join(', '));
      }
    });
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/js/local-search.js" ></script>

  <script defer src="/js/leancloud.js" ></script>





<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
</body>
</html>
